//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  SchedulerManager.cs
//    
// Abstract:
//
//  This module implements the SchedulerManager class. This class maintains the timer used for delivering reminders to the Dispatcher 
//  at the correct time.
//  
//--

using System;
using System.Diagnostics;
using System.Threading;

using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Scheduler
{
    /// <summary>
    /// Delivers reminders to the Dispatcher service at the correct time.
    /// </summary>
    public sealed class SchedulerManager : IDisposable
    {
        // Proxy to the Dispatcher service
        private IDispatcherProxy _dispatcherProxy;

        // Schedule of reminders
        private Schedule _schedule;

        // Timer used to send reminders to the Dispatcher service on time
        private Timer _timer;

        // Synchronizes access to the timer
        private readonly object _syncRoot = new object();

        /// <summary>
        /// ctor.
        /// </summary>
        internal SchedulerManager(ExchangeUsers exchangeUsers, IDispatcherProxy dispatcherProxy)
        {
            _dispatcherProxy = dispatcherProxy;

            _schedule = new Schedule(exchangeUsers);
            _schedule.Updated += Schedule_Updated;

            _timer = new Timer(new TimerCallback(TimeoutCallback));
        }

        /// <summary>
        /// Cleans up the SchedulerManager.
        /// </summary>
        /// <remarks>
        /// This method should only be called when the service is stoping.
        /// </remarks>
        public void Dispose()
        {
            lock (_syncRoot)
            {
                Debug.Assert(_timer != null, "Stop should not be called multiple times");

                _timer.Dispose();
                _timer = null;
            }
        }

        /// <summary>
        /// Updates the timer.
        /// </summary>
        /// <param name="sender">Not used</param>
        /// <param name="e">Not used</param>
        /// <remarks>
        /// This is invoked in response to the schedule changing.
        /// </remarks>
        private void Schedule_Updated(object sender, EventArgs e)
        {
            UpdateTimer();
        }

        /// <summary>
        /// Update the timer based on the time of the next scheduled reminder.
        /// </summary>
        private void UpdateTimer()
        {
            DateTime nextTime = _schedule.NextTime;
            DateTime now = DateTime.UtcNow;

            lock (_syncRoot)
            {
                if (_timer == null)
                {
                    // Don't update the timer because the service is stopping
                    return;
                }

                if (nextTime == DateTime.MaxValue)
                {
                    // Disable the timer because there is nothing in the schedule
                    _timer.Change(Timeout.Infinite, Timeout.Infinite);
                }
                else if (nextTime < now)
                {
                    // Fire the timer immediately because there are overdue notifications
                    _timer.Change(0, Timeout.Infinite);
                }
                else if (nextTime > DateTime.UtcNow.AddMilliseconds(Constants.MaxScheduleTimerSpan))
                {
                    // if the next time is over several days away, we'll just have the timer fire then as we're starting to
                    // get close to the maximum interval allowed by the System.Threading.Timer class.
                    _timer.Change(Constants.MaxScheduleTimerSpan, (long)Timeout.Infinite);
                }
                else
                {
                    // Set the timeout to deliver the next notification
                    TimeSpan interval = nextTime - now;
                    _timer.Change((long)interval.TotalMilliseconds, (long)Timeout.Infinite);
                }
            }
        }

        /// <summary>
        /// Timer callback.
        /// </summary>
        /// <param name="state">Not used</param>
        /// <remarks>
        /// This method delivers the reminders due at the current time.
        /// </remarks>
        private void TimeoutCallback(object state)
        {
            try
            {
                NotificationItem item = null;

                do
                {
                    DateTime nextTime = _schedule.NextTime;

                    item = null;

                    if (nextTime < DateTime.UtcNow + TimeSpan.FromSeconds(10))
                    {
                        // Only get notifications from the schedule that are due about now
                        item = _schedule.RemoveNext();
                    }

                    if (item != null)
                    {
                        _dispatcherProxy.SendNotification(item);
                    }
                }
                while (item != null);

                // Always re-start the timer
                UpdateTimer();
            }
            catch (Exception ex)
            {
                // Log all exceptions thrown here because they will lead to an unhandled exception and terminate the process
                Logger.WriteTraceError(ex, "TimeoutCallback failed.");

                // Assume all exceptions at this point are irrecoverable, so re-throw
                throw;
            }
        }
    }
}
